Skip to content

test(contract): sandbox tests for the attestation flow against the real tee-verifier - #4110

Closed
pbeza wants to merge 43 commits into
mainfrom
4084-verified-attestation-cross-contract
Closed

test(contract): sandbox tests for the attestation flow against the real tee-verifier#4110
pbeza wants to merge 43 commits into
mainfrom
4084-verified-attestation-cross-contract

Conversation

@pbeza

@pbeza pbeza commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Closes #4084

@claude

claude Bot commented Aug 10, 2026

Copy link
Copy Markdown

This PR includes changes to source code files in crates/tee-verifier/ and crates/tee-verifier-interface/. The test type should only be used when changing test files exclusively. Since source code is being modified, feat or refactor would be more appropriate.

Suggested title: feat(tee-verifier): add Verified attestation path cross-contract support

This comment was marked as outdated.

@claude

This comment was marked as outdated.

@pbeza
pbeza force-pushed the 4084-verified-attestation-cross-contract branch from b5e55c3 to faea559 Compare August 10, 2026 13:56
pbeza added 12 commits August 11, 2026 13:45
…tion

The MPC node generates its NEAR signer key inside the enclave and writes it
only to the CVM's encrypted disk. Collecting test assets needs that key
alongside the quote it is bound to, and an app-compose pre-launch script is
the only way in: the dev image ships no sshd and the guest agent serves only
node info and container logs.

vmm-cli already supports --prelaunch-script; this just plumbs an optional
PRELAUNCH_SCRIPT through. Attestation verification rejects any app-compose
carrying a script, so a CVM deployed this way cannot join a network - the
deploy guide, default.env and the script itself now say so loudly.
Collecting the attestation fixtures requires a pre-launch script to export
the node's in-enclave signer key, which makes the resulting app-compose one
that production verification rejects as arbitrary root code. The new
allow-pre-launch-script feature relaxes exactly that field for test builds;
init_script and bash_script stay rejected, and the production wasm never
enables it.

The check takes the policy as an argument so the strict behaviour keeps its
unit test in test builds too, and a further test pins the compiled-in policy
at the call site.
…ner key

Regenerated from a fresh localnet CVM on a TDX host, keeping the node's NEAR
signer key this time so tests can sign submit_participant_info as the fixture
node. The quote's report_data binds that key and the contract reads it from
the transaction signer, so without it the Verified store path is unreachable
(#3787).

create-assets.sh had been producing collateral our own parser cannot read
since the tee-verifier-interface refactor: nodes serialize the DER and
signature fields as JSON byte arrays while collateral_from_json expects hex
strings. It now hex-encodes those fields, and strips the NUL that terminates
the PEM chains so the fixture stays valid PEM. The regeneration reminder is
unconditional, since the committed key is normally present but stale.

.editorconfig no longer asserts the borsh fixture's final byte: it is
whatever the encoded collateral ends with.
… released artifacts

Implying allow-pre-launch-script from test-utils meant anything wanting only the
collateral_from_json parser also disabled the arbitrary-root-code check. The
feature is now named on the dependency edges that build tests, so every
enablement is greppable, and a new check fails the build if it reaches the
feature graph of the contract wasm, the node, attestation-cli (which verifies
attestations locally) or the verifier wasm.

Two tests replace one that could not fail: the committed keypair is checked
against near_account_public_key.pub, since create-assets.sh rewrites the public
key but cannot rewrite the secret; and the fixture app-compose is pinned as
satisfying the production policy once its export hook is removed, which is the
only place real attestation data meets the strict check.

Also drops doc claims about sandbox tests and a contract feature that live in
the follow-up, and records why the signer key has to be exported rather than
supplied to the node.
Replaces the helper that took the compile-time policy as a runtime argument
with `PRE_LAUNCH_SCRIPT_ALLOWED`, so every app-compose invariant reads inline
in `validate_app_compose_config`.

Drops the relaxation from this crate's own dev-dependency edge, so its unit
tests compile the production check. The two tests that only existed to pass
both values of the former argument go with it; CI builds `--all-features`, so
the strict branch was never compiled there anyway, and the feature-leak guard
is what keeps the relaxation out of released artifacts.
Multi-path updates replace the eight per-field pipeline stages, the hex
alphabet is bound once, and splitting on the NUL replaces the trim loop.

Records why the two committed fixtures encode the same bytes differently:
`public_data.json` is the endpoint response verbatim, so its byte fields are
JSON arrays, while the fixture parser reads hex.
Restores the Given/When/Then split in the fixture test, and stops the
localnet runbook from restating the collateral's `nextUpdate` date, which
goes stale on regeneration.
Removes `scripts/check-attestation-feature-leak.sh` and its `check-all-fast`
task. The comments that named it as the control now state the invariant it
checked: the relaxation is enabled only on dependency edges that no released
artifact builds.

Also corrects the `local-verify` comment, which claimed `mpc-contract` enables
it; the off-chain callers are the node, the attestation CLI and the TEE
authority.
Adds `export-signer-key-prelaunch.sh`, the `PRELAUNCH_SCRIPT` that exports the
node's in-enclave signer key, so the extraction is reproducible instead of
recoverable only from the fixture's measured app-compose.

`single-node.sh` now resolves a relative `PRELAUNCH_SCRIPT` against the repo
root: `deploy-launcher.sh` reads it from its own directory, so the documented
path was looked up two directories away and the deploy failed.

The runbook drops the steps duplicated from the asset README and gains the
paths the exported key actually lands in.
…hook

The key-export hook drops to 16 lines: the copy into the CVM's shared dir
could never work, since that mount is read-only in the guest, so the console
echo is the only channel. The comment header goes too, as the hook is embedded
verbatim in the measured app-compose.

Collected on a TDX host with the fixture's existing image digests, so the OS
and image measurements are unchanged and only the app-compose-derived values
move: `rtmr3`, `report_data`, the keys and the collateral. `mrtd` and
`rtmr0`-`rtmr2` still match `mpc-attestation`'s compiled-in dev measurements.

`VALID_ATTESTATION_TIMESTAMP` moves into the new collateral's validity window,
which now opens after the previous pinned value.
pbeza added 4 commits August 11, 2026 15:18
Nothing else in these scripts rewrites a user-supplied relative path:
`BASE_PATH` is used verbatim, and `ENV_TPL`/`CONF_TPL` only default to paths
under the repo root. Resolving this one made it behave unlike its neighbours,
so the runbook passes an absolute path instead and `deploy-launcher.sh` fails
fast when it does not exist.

That leaves `single-node.sh` unchanged from main: re-exporting the variable was
a no-op, since a value set on the command line is already in the environment
that `deploy-launcher.sh` inherits.

Also drops the commented-out `PRELAUNCH_SCRIPT` from `default.env`, which the
collection flow never reads — `single-node.sh` overrides `--env-file` — and
trims the warning at the point of use.
Measured rather than guessed: disabling the feature and running without
`--all-features` fails six `validated_dstack_*` tests in `mpc-attestation`, two
in `attestation-cli`, and five in `mpc-contract`, since the regenerated
fixture's app-compose carries the export hook. Noted where they cluster — two
test-file headers, the shared `dstack_verification_setup` helper, and one test —
rather than repeated on all thirteen.

Also renames the fixture test to say the hook is cleared, and rewords the
feature comments that read awkwardly.
The feature sat on `test-utils`' normal dependency edge, so any invocation
whose root set includes that crate — `cargo build --workspace --release`, for
one — unified it into the single `attestation` lib that `mpc-node` and the
contract link. Nothing ships that way today, but a comment was the only thing
saying so.

It now forwards through `mpc-contract` and `attestation-cli` features enabled
only where those crates build their own tests, matching what `mpc-attestation`
already did. `test-utils` no longer needs it: its one verification call is the
DCAP step, which never reaches app-compose validation.

Also fixes the `VALID_ATTESTATION_TIMESTAMP` comment, which still named the
previous value's date, and restores the `local-verify` documentation dropped
earlier in the branch.
/// (spelled in full because the import is feature-gated). The pin exists
/// because sandbox chain time is wall-clock and forward-only, so it can never
/// fall inside the validity window of a checked-in collateral fixture.
fn now_seconds() -> u64 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Production compiles this down to just the block timestamp, so the gating itself is right. But sandbox-test-hooks is a public feature, so a --all-features build turns it on — and here that means expired collateral verifies.

Worth making it unbuildable rather than just unused:

#[cfg(all(target_arch = "wasm32", feature = "sandbox-test-hooks"))]
compile_error!("never enable in a wasm build");

@pbeza
pbeza force-pushed the 4084-verified-attestation-cross-contract branch 2 times, most recently from 4b7ea55 to ab45f90 Compare August 12, 2026 16:23
pbeza added 2 commits August 14, 2026 09:59
…y method

The fixture is captured from a CVM whose launcher compose carries the service
that exported its signer key, so its compose hash is not derivable from the
compiled-in template and no vote can allow it. `sandbox-test-attestation` now
gates a method that whitelists that one hash, replacing the app-compose policy
relaxation the feature used to enable.
`verify_quote` takes `&self`, so near-sdk reads the STATE key on every call:
production builds never read the pinned-timestamp key, but they do read
storage. The E2E task runner builds five things, not three, and passes three
WASM paths. The design doc's status line pointed at the shipped design as if it
were the superseded one.
@pbeza
pbeza force-pushed the 4084-verified-attestation-cross-contract branch from 59555e8 to 4e6f821 Compare August 14, 2026 08:24
@claude

This comment was marked as low quality.

Base automatically changed from 3787-regenerate-fixtures to main August 14, 2026 11:10
@claude

This comment was marked as low quality.

@claude

This comment was marked as low quality.

@pbeza

pbeza commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

This comment was marked as outdated.

@claude

This comment was marked as low quality.

The verifier's sandbox-test-hooks feature now takes effect only together
with --cfg mpc_sandbox_wasm, which only the test harness sets on the wasm
it builds; a wasm carrying the feature without the marker fails to
compile. The contract-side sandbox-test-attestation feature and its
whitelisting method are gone: sandbox tests patch the fixture's compose
hash straight into contract state through a host-only test-utils helper,
so the deployed contract is the production-shape build. Two new tests pin
the released behavior: the production verifier ignores the pinned
timestamp, and a verified quote whose compose hash was never whitelisted
stores nothing.
@pbeza pbeza changed the title test(contract): cover the Verified attestation path cross-contract in sandbox test(contract): sandbox tests for the attestation flow against the real tee-verifier Aug 17, 2026
@claude

claude Bot commented Aug 17, 2026

Copy link
Copy Markdown

PR title type suggestion: This PR changes source code files (e.g., crates/contract/src/tee/proposal.rs, crates/tee-verifier/src/lib.rs), not just test files, so the type prefix should probably be feat: instead of test:.

Suggested title: feat(contract): implement Verified attestation path for cross-contract testing in sandbox

@claude

claude Bot commented Aug 17, 2026

Copy link
Copy Markdown

PR title type suggestion: This PR modifies source code files across multiple crates, so the type prefix should probably be feat: instead of test:.

Suggested title: feat(tee-verifier): add sandbox tests for attestation flow with real tee-verifier

@claude

claude Bot commented Aug 17, 2026

Copy link
Copy Markdown

PR title type suggestion: This PR modifies source code files across multiple crates, so the type should be feat: instead of test:.

Suggested title: feat(contract): add sandbox tests for attestation flow against the real tee-verifier

pbeza added 2 commits August 17, 2026 17:28
The crate convention is a test_utils submodule per domain module, and the
helper edits the launcher allowlist, which lives under tee. Also drops the
round-trip assert: borsh encoding is canonical, so deserializing and
reserializing the same type is byte-identical by construction.
@claude

claude Bot commented Aug 17, 2026

Copy link
Copy Markdown

PR title type suggestion: This PR includes changes to source code files in addition to test files. The test type is for test-only changes. Could you verify if the type should be feat or refactor to reflect the scope of source code modifications?

pbeza added 2 commits August 17, 2026 22:55
Collapses the repeated setup and voting scaffolding: setup() and
setup_verified_fixture() return the handles the tests bind anyway,
all_vote() takes the vote to cast, prepay_grant_from_separate_payer()
returns the balance its callers read next, and pin_verifier_clock()
replaces the patch_state blocks. Reuses submit_dstack, get_config and
cluster_poll_retry where the code hand-rolled them, and drops the sha2
dependency for the near-kit hash already used a few lines away.
@claude

claude Bot commented Aug 17, 2026

Copy link
Copy Markdown

This PR adds new crates (tee-verifier, tee-verifier-interface) with implementation code, not just tests. The type prefix should probably be feat: instead of test: to reflect the new functionality being added.

Suggested title: feat(tee-verifier): add sandbox tests for attestation flow against real verifier

…helper

Every lookup in the file asks for the same TLS key, so keep the key in
one place instead of restating it at each call site.
@pbeza

pbeza commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Closing in favor of #4165.

@pbeza pbeza closed this Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sandbox tests for the attestation flow against the real tee-verifier contract

3 participants